﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using WorldView;
using SpecialEffects;
using Microsoft.Xna.Framework.Graphics;
using GlobalGameClasses;
using System;
using Sirian.DrawableObject.Actors;
using System.Collections.Generic;

namespace DrawableObjects.Actors
{
    /// <summary>
    /// This class is the base of every object which contains a model. 
    /// It controls the movement and the collisions of an obejct.
    /// </summary>
    public abstract class Actor : DrawableObject
    {
        // Floating: is moved along with the game screen
        // Unremoveable: is not removed if bounding is left
        public enum ActorAttributes { Floating, Unremoveable };

        public List<ActorAttributes> actorAttributes;

        public Bounding worldBounding;

        protected bool faceMoveDirection = false;

	    public Owner owner
	    {
		    get;
		    set;
	    }

	    public World world
	    {
		    get;
		    set;
	    }

	    public float speed
	    {
		    get;
		    set;
	    }

	    protected SoundEffectInstance Sound_OnDestroy
	    {
		    get;
		    set;
	    }

        private string contentKey;

        public Model model
        {
            get;
            private set;
        }

        public Texture2D texture 
        {
            get;
            private set;
        }

        public float boundingSphereFactor
        {
            get;
            protected set;
        }

        public BoundingSphere boundingSphere
        {
            protected set;
            get;
        }

        public Matrix standardRotation = Matrix.Identity;

        public float scaleFactor
        {
            protected set;
            get;
        }

        protected Vector2 moveDirection
        {
            get;
            set;
        }

        protected Actor(string strTextureModel, Matrix worldMatrix, float scaleFactor, float boundingFactor) : base(worldMatrix)
        {
            this.boundingSphereFactor = boundingFactor;
            this.contentKey = strTextureModel;
            this.scaleFactor = scaleFactor;
            this.actorAttributes = new List<ActorAttributes>();
        }

        /// <summary>
        /// Calculates the radius of a model.
        /// </summary>
        private float MeasureModel(Model model)
        {
            float modelRadius = 0;
            if (model != null)
            {
                Matrix[] boneTransforms = new Matrix[model.Bones.Count];

                model.CopyAbsoluteBoneTransformsTo(boneTransforms);

                Vector3 modelCenter = Vector3.Zero;

                foreach (ModelMesh mesh in model.Meshes)
                {
                    BoundingSphere meshBounds = mesh.BoundingSphere;
                    Matrix transform = boneTransforms[mesh.ParentBone.Index];
                    Vector3 meshCenter = Vector3.Transform(meshBounds.Center, transform);

                    modelCenter += meshCenter;
                }

                modelCenter /= model.Meshes.Count;
                
                foreach (ModelMesh mesh in model.Meshes)
                {
                    BoundingSphere meshBounds = mesh.BoundingSphere;
                    Matrix transform = boneTransforms[mesh.ParentBone.Index];
                    Vector3 meshCenter = Vector3.Transform(meshBounds.Center, transform);

                    float transformScale = transform.Forward.Length();

                    float meshRadius = (meshCenter - modelCenter).Length() + (meshBounds.Radius * transformScale);

                    modelRadius = Math.Max(modelRadius, meshRadius);
                }
            }
            return modelRadius;
        }


        public event GlobalGameClasses.Eventhandler.CollideEventHandler Collide;

	    public event GlobalGameClasses.Eventhandler.DestroyEventHandler Destroy;

	    public event GlobalGameClasses.Eventhandler.RemoveEventHandler Remove;

        public event GlobalGameClasses.Eventhandler.CreateEventHandler Create;


        public abstract void collide(Actor actor2);

	    private void AddSpecialEffect(SpecialEffect effect)
	    {
            world.addSpecialEffect(effect);
	    }

        /// <summary>
        /// Moves the object towards target location.
        /// </summary>
        /// <param name="target">target location is a Vector2D because the height is constant in our game</param>
	    public void moveTowards(Vector2 target)
	    {
            Vector3 trans = worldMatrix.Translation;
            float dirX = target.X - trans.X;
            float dirZ = target.Y - trans.Z;
            Vector2 direction = new Vector2(dirX, dirZ);

            moveInDirection(direction);
	    }

        /// <summary>
        /// Moves the object towards the specified direction.
        /// </summary>
        /// <param name="direction">direction is a Vector2D because the height is constant in our game</param>
	    public void moveInDirection(Vector2 direction)
	    {
            moveDirection = new Vector2(direction.X, direction.Y);
	    }

	    public void removeFromWorld(bool leftBounds)
	    {
            world.removeActor(this);
            if (Remove != null)
            {
                Remove(this, leftBounds);
            }
	    }

        protected void destroy(Actor destroyedBy)
        {
            world.removeActor(this);
            if (Remove != null)
            {
                Remove(this, false);
            }
            if (Destroy != null)
            {
                Destroy(this, destroyedBy);
            }
        }

        protected void collideWith(Actor collidedWith)
        {
            Collide(this, collidedWith);
        }

        /// <summary>
        /// Moves the object towards moveDirection and checks if the object has left its bounding and should be deleted.
        /// </summary>
        public override void Update()
        {
            float speedFactor = (float)(GlobalGameClasses.GlobalGameValues.Values.gameTime.ElapsedGameTime.TotalMilliseconds) * GlobalGameValues.SPEEDFACTOR;
            float maxSpeed = moveDirection.Length();
            if (moveDirection != Vector2.Zero && maxSpeed > speed)
            {
                moveDirection = Vector2.Normalize(moveDirection);
                moveDirection = Vector2.Multiply(moveDirection, speed);
            }
            moveDirection = Vector2.Multiply(moveDirection, speedFactor);
            Vector3 trans;
            Vector2 currentPosition = getGamePosition();
            if (!worldBounding.isBinding || worldBounding.isWithinBounding(Vector2.Add(currentPosition, moveDirection), this.boundingSphere.Radius))
            {
                trans = new Vector3(moveDirection.X, 0, moveDirection.Y);
                trans = Vector3.Add(worldMatrix.Translation, trans);
                this.worldMatrix = standardRotation * Matrix.CreateScale(scaleFactor) * Matrix.CreateWorld(trans, Vector3.Backward, Vector3.Up);
            }
            else if (isInBoundings())
            {
                Vector2 alternativePosition = worldBounding.getClosestPointWithin(getGamePosition(), moveDirection, boundingSphere.Radius);
                trans = new Vector3(alternativePosition.X, GlobalGameValues.GAMEFIELD_HEIGHT, alternativePosition.Y);
                this.worldMatrix = standardRotation * Matrix.CreateScale(scaleFactor) * Matrix.CreateWorld(trans, Vector3.Backward, Vector3.Up);
            }
            if (faceMoveDirection && moveDirection != Vector2.Zero)
            {
                float moveAngle = (float)Math.Atan2(moveDirection.X, moveDirection.Y);
                this.worldMatrix = Matrix.CreateRotationY(moveAngle) * worldMatrix;
            }

            moveDirection = Vector2.Zero;
            this.boundingSphere = new BoundingSphere(worldMatrix.Translation, boundingSphereFactor);
        }

        public bool isInBoundings()
        {
            return worldBounding.isWithinBounding(this.getGamePosition(), this.boundingSphere.Radius);
        }

        public virtual void Initialize()
        {
            Model model;
            Texture2D tex = null;
            if (!(GlobalGameValues.Values.content.Models.TryGetValue(contentKey, out model)
                && GlobalGameValues.Values.content.Textures.TryGetValue(contentKey, out tex)))
            {
                throw new System.ArgumentException("Couldn't find Model or Texture: " + contentKey);
            }
            this.model = model;
            this.texture = tex;
            this.boundingSphereFactor *= scaleFactor * MeasureModel(model) / 2;
            this.boundingSphere = new BoundingSphere(worldMatrix.Translation, boundingSphereFactor);
            this.worldMatrix = standardRotation * Matrix.CreateScale(scaleFactor) * Matrix.CreateWorld(worldMatrix.Translation, Vector3.Backward, Vector3.Up);
            if (Create != null)
            {
                Create(this);
            }
        }
    }
}


